﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Management;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using CSScriptLibrary;
using RoMBot.Classes;
using RoMBot.Database;
using RoMBot.Properties;
using RoMBot.Waypoints;
using RoMBot.MemoryLib;
using ThreadState = System.Threading.ThreadState;

namespace RoMBot
{
    public class Bot
    {
        private static Bot _instancia;
        private static Skills _skills;
        private static WaypointList _waypoints = new WaypointList();
        private static WaypointList _returnWaypoints = new WaypointList();
        public static string DataPath = Environment.CurrentDirectory + "\\"; //Environment.GetFolderPath( Environment.SpecialFolder.MyDocuments ) + "\\RoMBot\\";
        internal const int IgfCurrentVersion = 6; // Change this value to match the value in "ingamefunctions.lua".
        //This is to keep track of loot and give some statistics.
        private static Inventory _startInventory;
        public static event EventHandler BotInitializated;
        public static event EventHandler BotPaused;
        public static event EventHandler BotStopped;
        public static event EventHandler<GameInstanceAddedEventArgs> BotGameInstanceAdded;
        public static event EventHandler BotStartedOrResumed;
        
        private static void OnBotStartedOrResumed()
        {
            var handler = BotStartedOrResumed;
            if ( handler != null )
            {
                handler( null, EventArgs.Empty );
            }
        }

        private static void OnBotGameInstanceAdded( GameInstanceAddedEventArgs e )
        {
            var handler = BotGameInstanceAdded;
            if ( handler != null )
            {
                handler( null, e );
            }
        }

        private static void OnBotPaused()
        {
            var handler = BotPaused;
            if ( handler != null )
            {
                handler( null, EventArgs.Empty );
            }
        }

        private static void OnBotStopped()
        {
            var handler = BotStopped;
            if ( handler != null )
            {
                handler( null, EventArgs.Empty );
            }
        }

        private static void OnInitializated()
        {
            var handler = BotInitializated;
            if ( handler != null )
            {
                handler( null, EventArgs.Empty );
            }
        }

        internal const string BaseScriptDef =
        @"namespace RoMBot
            {{
                using System;
                using System.Drawing;
                using System.Diagnostics;
                using System.Threading;
                using System.Collections.Generic;
                using RoMBot.Classes;
                using RoMBot.Database;
                using RoMBot.Waypoints;
                using RoMBot.MemoryLib;

                public static class UserFunction
                {{
                    public static Bot Bot {{ get {{ return RoMBot.Bot.Instancia; }} }}
                    public static Player Player {{ get {{ return RoMBot.Bot.Player; }} }}
                    public static Func<string, object> RoMScript {{ get {{ return RoMBot.Helpers.RoMScript; }} }}
                    
                    {0}
                }}
            }}";


        private static readonly Dictionary<string, Func<object, object>> UserFunctionsDict = new Dictionary<string, Func<object, object>>();
        public static readonly Dictionary<string, int> GameInstances = new Dictionary<string, int>();

        static Bot()
        {
            UseDatabaseHttpServer = true;
            var i = SProcess.GetProcessesFromWindowTitle( "Runes of Magic" );
            if ( i == null )
            {
                return;
            }
            if ( i.Length > 1 )
            {
                foreach ( var d in i )
                {
                    Memory.Instance.ProcId = 0;
                    Instance.ProcId = d;
                    var p = new Player( true );
                    if ( p.Name != GameObject.UnknowName )
                    {
                        GameInstances.Add( p.Name, d );
                    }
                }
                Instance.ProcId = 0;
                Memory.Instance.ProcId = 0;
            }
            else
            {
                Instance.ProcId = i[ 0 ];
                var p = new Player( true );
                if ( p.Name != GameObject.UnknowName )
                {
                    GameInstances.Add( p.Name, i[ 0 ] );
                }
            }

            //ProcessStart.Priority = ThreadPriority.Lowest;
            //ProcessStart.Start();
        }
        //private static readonly Thread ProcessStart = new Thread( CheckProcessStart );
        public static void CheckProcessStart()
        {
            var i = SProcess.GetProcessesFromWindowTitle( "Runes of Magic" );
            if ( i == null )
            {
                return;
            }
            if ( i.Length > 0 )
            {
                foreach ( var d in i )
                {
                    if ( !GameInstances.ContainsValue( d ) )
                    {
                        var prPtr = SProcess.OpenProcess( d );
                        if ( prPtr != IntPtr.Zero )
                        {
                            var addr = SMemory.ReadInt( prPtr, Addresses.staticbase_char );
                            if ( addr != 0 )
                            {
                                addr = SMemory.ReadInt( prPtr, ( uint ) ( addr + Addresses.charPtr_offset ) );
                                if ( addr != 0 )
                                {
                                    var namePtr = SMemory.ReadUInt( prPtr, ( uint ) ( addr + Addresses.pawnName_offset ) );
                                    if ( namePtr != 0 )
                                    {
                                        var name = SMemory.ReadUTF8String( prPtr, namePtr );
                                        if ( !string.IsNullOrEmpty( name ) )
                                        {
                                            GameInstances.Add( name, d );
                                            OnBotGameInstanceAdded( new GameInstanceAddedEventArgs( name ) );
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        public void StopCheckingForNewInstances()
        {
        }

        private Bot() { }
        public static string ForcedProfile { get; set; }
        private bool initializing;
        public bool Initialize()
        {
            if ( this.Initialized || initializing )
            {
                return true;
            }
            initializing = true;

            if ( ProcId == 0 )
            {
                throw new Exception( "ProcId not set" );
            }

            Stopped = true;

            this.CheckDirectories();

            _skills = new Skills();

            Macros.SetupMacros();
            Database.Database.Load();
            CheckIgfAddon();

            SetupUserFunctions();

            Task.Factory.StartNew( () =>
                                   {
                                       _startInventory = new Inventory();
                                       _startInventory.Update();
                                   } );

            Printf( Color.Yellow, "Initializing new player" );
            new Player();

            Task.Factory.StartNew( () =>
            {
                if ( Player.SurroundingObjectsTable == null )
                    Player.SurroundingObjectsTable = ObjectsTable.Instancia;
            } );

            this.Stopped = true;

            Player.LevelDetectLevelup = Player.Level;
            tThread = new Thread( Run );

            keyboardThread = new Thread( this.CheckKeyboard ) { Priority = ThreadPriority.Lowest };

            this.Initialized = true;
            initializing = false;
            OnInitializated();
            Printf( Color.Yellow, "Done initializing, bot ready." );
            return true;
        }

        private static void CheckIgfAddon()
        {
            // check if ( igf addon is active
            var igfVersion = ( int ) Helpers.RoMScript( "IGF_INSTALLED" );
            if ( igfVersion > 0 )
            {
                IgfAddon = true;
                IgfVersion = igfVersion;
                // Check version
                if ( igfVersion != IgfCurrentVersion )
                {
                    Error( string.Format( Resources.IgfDifferentVersion, IgfCurrentVersion, igfVersion ) );
                }
            }
            else
            {
                Error( Resources.IgfNotInstalled ); // Ingamefunctions addon (igf) is not installed
                throw new Exception( Resources.IgfNotInstalled );
            }

            Print( Resources.TestingIgf );
            var hfReturn = Helpers.RoMScript<int>( "1234;ChatFrame1:AddMessage(\"MACRO test: send value 1234 to macro place 2\");" );
            if ( hfReturn != 1234 )
            {
                Error( Resources.MacroComunicationNotWorking );
                throw new Exception( Resources.MacroComunicationNotWorking );
            }

            Printf( Color.Green, "MACRO Test: ok." );
            Helpers.RoMScript( "xxxx; ChatFrame1:AddMessage(\"MACRO test: successful\");" );	// overwrite values
        }

        public static int IgfVersion { get; private set; }

        public static bool IgfAddon { get; private set; }

        private static void SetupUserFunctions()
        {
            Helpers.Yrest( 1 );
            var userFunctionFiles = Directory.GetFiles( DataPath + "\\UserFunctions", "*.cs" );
            var userClassesFiles = Directory.GetFiles( DataPath + "\\UserFunctions\\Classes", "*.cs" );
            if ( userFunctionFiles.Any() )
            {
                Printf( Color.Yellow, "Setting up userfunctions" );
                foreach ( var userFunctionFile in userFunctionFiles )
                {
                    CSScript.LoadCode( String.Format( BaseScriptDef, File.ReadAllText( userFunctionFile, Encoding.UTF8 ) ) );
                    Helpers.Yrest( 1 );
                }
            }

            if ( userClassesFiles.Any() )
            {
                Printf( Color.Yellow, "Setting up userfunctions classes" );
                foreach ( var userClassFile in userClassesFiles )
                {
                    CSScript.LoadCode( File.ReadAllText( userClassFile, Encoding.UTF8 ) );
                    Helpers.Yrest( 1 );
                }
            }
        }

        public void CheckDirectories()
        {
            if ( !Directory.Exists( DataPath + "\\Database" ) )
            {
                Directory.CreateDirectory( DataPath + "\\Database" );
                File.WriteAllText( DataPath + "\\Database\\skills.xml", Resources.skills, Encoding.UTF8 );
                File.WriteAllText( DataPath + "\\Database\\giftbags.xml", Resources.giftbags, Encoding.UTF8 );
                File.WriteAllText( DataPath + "\\Database\\consumables.xml", Resources.consumables, Encoding.UTF8 );
                File.WriteAllText( DataPath + "\\Database\\nodes.xml", Resources.nodes, Encoding.UTF8 );
            }
            if ( !Directory.Exists( DataPath + "\\UserFunctions" ) )
            {
                Directory.CreateDirectory( DataPath + "\\UserFunctions" );
            }
            if ( !Directory.Exists( DataPath + "\\UserFunctions\\Classes" ) )
            {
                Directory.CreateDirectory( DataPath + "\\UserFunctions\\Classes" );
            }
            if ( !Directory.Exists( DataPath + "\\Waypoints" ) )
            {
                Directory.CreateDirectory( DataPath + "\\Waypoints" );
            }
            if ( !Directory.Exists( DataPath + "\\Profiles" ) )
            {
                Directory.CreateDirectory( DataPath + "\\Profiles" );
                File.WriteAllText( DataPath + "\\Profiles\\Default.xml", Resources.Default, Encoding.UTF8 );
            }
            if ( !Directory.Exists( DataPath + "\\Screenshots" ) )
            {
                Directory.CreateDirectory( DataPath + "\\Screenshots" );
            }
        }

        public void Start()
        {
            this.Stopped = false;
            this.Paused = false;
            Player.Running = true;

            if ( this.Running )
                return;

            Debug( Resources.BotStarted );

            Player.ResetAllTasks();
            var t = Task.Factory.StartNew( () =>
                                   {
                                       Player.Update();
                                       Player.Inventory.Update();
                                   } );
            //KeyBoardTask = Task.Factory.StartNew( CheckKeyboard );
            keyboardThread = new Thread( CheckKeyboard );
            keyboardThread.Start();
            t.Wait( 100 );
            if ( ReturnWaypointList.Count > 0 )
            {
                if ( Player.DistanceToWaypoint( ReturnWaypointList.GetNearestWaypoint( Player.X, Player.Z ) ) < Player.DistanceToWaypoint( WaypointList.GetNearestWaypoint( Player.X, Player.Z ) ) )
                {
                    Printf( Color.Tomato, Resources.ReturnPathIsCloser );
                    Player.Returning = true;
                }
            }

            if ( !Player.Returning )
            {
                Instancia.WaypointList.Current = Instancia.WaypointList.GetNearestWaypoint( Player.X, Player.Z );
            }
            else
            {
                Instancia.ReturnWaypointList.Current = Instancia.ReturnWaypointList.GetNearestWaypoint( Player.X, Player.Z );
            }
            RealTimer.Start();
            RunStart();
        }

        private void CheckKeyboard()
        {
            while ( !Stopped )
            {
                try
                {
                    if ( !CreatingWaypointList && !AssistMode )
                    {
                        if ( Keyboard.IsKeyPressedInApp( Key.VK_NUMPAD1 ) )
                        {
                            Keyboard.ReleaseKey( Key.VK_NUMPAD1 );
                            Thread.Sleep( 0 );
                            WaypointList.Insert( Player.X, Player.Z, Player.Y );
                            Helpers.SendMessage( string.Format( "Inserted Normal Waypoint at index #{0}", WaypointList.Current.Index ) );
                        }
                        if ( Keyboard.IsKeyPressedInApp( Key.VK_NUMPAD2 ) )
                        {
                            Keyboard.ReleaseKey( Key.VK_NUMPAD2 );
                            Thread.Sleep( 0 );
                            WaypointList.Insert( Player.X, Player.Z, Player.Y, "Player.Harvest();" );
                            Helpers.SendMessage( string.Format( "Inserted Harvest Waypoint at index #{0}", WaypointList.Current.Index ) );
                        }
                        if ( Keyboard.IsKeyPressedInApp( Key.VK_NUMPAD5 ) )
                        {
                            Keyboard.ReleaseKey( Key.VK_NUMPAD5 );
                            Thread.Sleep( 0 );
                            var wp = WaypointList.GetNextWayPoint( -1 );
                            var index = wp.Index;
                            WaypointList.Waypoints.Remove( wp );
                            Helpers.SendMessage( string.Format( "Waypoint deleted ({0})", index ) );
                        }
                        if ( Keyboard.IsKeyPressedInApp( Key.VK_NUMPAD4 ) )
                        {
                            Keyboard.ReleaseKey( Key.VK_NUMPAD4 );
                            Thread.Sleep( 0 );
                            var wp = WaypointList.Current;
                            var index = wp.Index;
                            WaypointList.Waypoints.Remove( wp );
                            Helpers.SendMessage( string.Format( "Waypoint deleted ({0})", index ) );
                        }
                        if ( Keyboard.IsKeyPressedInApp( Key.VK_NUMPAD9 ) )
                        {
                            Keyboard.ReleaseKey( Key.VK_NUMPAD9 );
                            Thread.Sleep( 0 );
                            WaypointList.Save();
                            Helpers.SendMessage( string.Format( "WaypointList saved ({0})", WaypointList.FileName ) );
                        }
                    }
                    if ( AssistMode )
                    {
                        if ( Keyboard.IsKeyPressedInApp( Key.VK_NUMPAD5 ) )
                        {
                            Keyboard.ReleaseKey( Key.VK_NUMPAD5 );
                            Player.Harvest();
                        }
                    }
                    if ( !Paused && Keyboard.IsKeyPressedInApp( Key.VK_END ) )
                    {
                        Thread.CurrentThread.Join( 10 );
                        Keyboard.ReleaseKey( Key.VK_END );
                        Thread.CurrentThread.Join( 50 );
                        if ( RunningUserCode )
                        {
                            userCodeCancelationSource.Cancel();
                        }
                        else
                        {
                            Pause();
                        }
                    }
                    Thread.CurrentThread.Join( 50 );
                    if ( Paused && Keyboard.IsKeyPressedInApp( Key.VK_DELETE ) )
                    {
                        Thread.CurrentThread.Join( 10 );
                        Keyboard.ReleaseKey( Key.VK_DELETE );
                        Thread.CurrentThread.Join( 50 );
                        if ( !RunningUserCode )
                        {
                            Resume();
                        }
                        else
                        {
                            Paused = false;
                        }
                    }
                    Thread.CurrentThread.Join( 100 );
                }
                catch ( ThreadInterruptedException )
                {
                    Warning( "Keyboard interrupted" );
                }
            }
        }

        public bool CreatingWaypointList { get; set; }

        private void RunStart()
        {
            ClearProgressBar();
            tThread = new Thread( Run );
            tThread.Start();
            OnBotStartedOrResumed();
        }

        public static TimeSpan RunningTime
        {
            get { return RealTimer.Elapsed; }
        }

        public static bool AssistMode { get; set; }

        private bool RunningUserCode { get; set; }

        public static event EventHandler<ProgressbarUpdatedArgs> ProgressBarUpdate;

        public static void UpdateProgressBar( int value, int max = 100, string text = null, bool finalize = false )
        {
            var pbu = ProgressBarUpdate;
            if ( pbu != null )
            {
                pbu( null, new ProgressbarUpdatedArgs { Value = value, Max = max, Text = text ?? "", Finalize = finalize } );
            }
        }

        public static void ClearProgressBar()
        {
            UpdateProgressBar( 0, finalize: true );
        }

        CancellationTokenSource userCodeCancelationSource = new CancellationTokenSource();
        public void RunUserCode( string code )
        {
            try
            {
                var m = CSScript.LoadMethod( "using RoMBot;using RoMBot.Classes;using RoMBot.Database;using RoMBot.Waypoints;" + Environment.NewLine + code ).GetStaticMethod();
                this.Stopped = false;
                this.Paused = false;
                userCodeCancelationSource = new CancellationTokenSource();
                userCodeCancelationSource.Token.Register( () => Warning( Resources.UserCodeExecutionCancelled ) );
                keyboardThread = new Thread( CheckKeyboard );
                Player.ResetAllTasks();
                Task.Factory.StartNew( () =>
                                  {
                                      try
                                      {
                                          RunningUserCode = true;
                                          //KeyBoardTask = Task.Factory.StartNew( CheckKeyboard );
                                          keyboardThread.Start();
                                          m();
                                      }
                                      catch ( Exception e )
                                      {
                                          Error( e.Message );
                                      }
                                      finally
                                      {
                                          this.Stopped = true;
                                          RunningUserCode = false;
                                      }
                                  }, userCodeCancelationSource.Token );
            }
            catch ( Exception e )
            {
                Error( e.Message );
            }
        }

        public WaypointList CurrentWaypointList
        {
            get { return Player.Returning ? ReturnWaypointList : WaypointList; }
        }

        private static readonly Stopwatch RealTimer = new Stopwatch();

        private void Run()
        {
            this.Running = true;
            Player.Running = true;
            if ( Player.SurroundingObjectsTable == null )
                Player.SurroundingObjectsTable = ObjectsTable.Instancia;
            //Bot.Player.TraverseWaypointList( this.WaypointList );
            while ( !this.Stopped )
            {
                try
                {
                    Player.CheckAddress();

                    if ( this.Paused )
                    {
                        Thread.CurrentThread.Join( 250 );
                        continue;
                    }
                    Player.Fighting = false;

                    if ( !Player.Alive )
                    {
                        Player.ResetSkillLastCastTime(); // set last use back, so we can rebuff
                        this.Resurrect();
                    }

                    Task.Factory.StartNew( () =>
                                           {
                                               if ( Player.Profile.ReloadAmmunition && Player.Equipment.AmmunitionCount == 0 )
                                               {
                                                   Player.Inventory.ReloadAmmunition( Player.Profile.ReloadAmmunitionType );
                                               }
                                           } );

                    // update inventory if update flag is set
                    // TODO: rolling update while resting?
                    if ( Player.InventoryDoUpdate && !Player.Battling )
                    {
                        Player.InventoryDoUpdate = false;
                        // remember update time 
                        Player.InventoryLastUpdate = DateTime.Now;
                        Player.Inventory.Update();
                    }

                    if ( this.CurrentWaypointList.Type != WaypointType.Travel )
                    {
                        Player.UpdateMP();

                        var manaRest = false;
                        if ( Player.MaxMana > 0 )
                        {
                            manaRest = ( ( Player.Mana / Player.MaxMana ) * 100 ) < Player.Profile.GetOptionValue<int>( "MP_REST" );
                        }
                        var healthRest = ( ( Player.HP / Player.MaxHP ) * 100 ) < Player.Profile.GetOptionValue<int>( "HP_REST" );

                        if ( manaRest || healthRest )
                        {
                            Player.Rest( 50, 99, "full", 10 );		// rest before next fight
                        }
                    }

                    // If aggro, look for target
                    if ( Player.Battling )
                    {

                        if ( this.CurrentWaypointList.Type == WaypointType.Travel )
                        {
                            Print( Resources.WpTypeTravelDontStopDontFightBack, Color.Green );
                        }
                        else if ( Player.SetTarget( Player.FindEnemy( true, 0, Player.EvalTargetDefault ) ) )
                        {
                            Print( Resources.GotAggroAtacckingAgressiveEnemies, Color.Green );
                            Thread.CurrentThread.Join( 10 );
                        }
                    }

                    if ( AssistMode )
                    {
                        if ( !Player.Mounted )
                        {
                            if ( Player.CheckPotions() | Player.CheckSkills( true ) )
                            {
                                Player.ClearTarget();
                            }
                        }

                        if ( Player.HasTarget || Player.HasAggro )
                        {
                            if ( Player.targetPtr == Player.Address )
                            {
                                Player.ClearTarget();
                            }
                            else
                            {
                                Print( "Enemy spotted!", Color.Green );
                                Player.Fight();
                            }
                        }
                        Thread.CurrentThread.Join( 10 );
                    }
                    else
                    {
                        if ( ( this.CurrentWaypointList.Current.Type != WaypointType.Travel ) && Player.HasTarget )
                        {
                            // only fight back if it's not a TRAVEL waypoint
                            // remember players position at fight start
                            var fightStartX = Player.X;
                            var fightStartZ = Player.Z;

                            // Other check were done in defaultEvalFunction
                            Player.Fight();

                            if ( this.CurrentWaypointList.Current.Type != WaypointType.Wander )
                            {
                                var currentWp = this.CurrentWaypointList.Current;

                                // calculate direction in rad for: fight start postition -> current waypoint
                                var dirFightstartToCurrentwp = Math.Atan2( currentWp.Z - fightStartZ, currentWp.X - fightStartX );
                                var distFightstartToCurrentwp = Helpers.Distance( fightStartX, fightStartZ, currentWp.X, currentWp.Z );

                                // calculate direction in rad for: fight start postition -> fight end postition
                                var dirFightstartToFightend = Math.Atan2( Player.Z - fightStartZ, Player.X - fightStartX );
                                var distFightstartToFightend = Helpers.Distance( Player.X, Player.Z, fightStartX, fightStartZ );

                                // calculate how much  fighstart, wp and fightend are on a line, 0 = one line,
                                var angleDif = Helpers.AngleDifference( dirFightstartToCurrentwp, dirFightstartToFightend );

                                var a = distFightstartToCurrentwp;
                                var b = distFightstartToCurrentwp;
                                var ga = angleDif;
                                var distToPassedWp = Math.Sqrt( Math.Pow( a, 2 ) + Math.Pow( b, 2 ) - 2 * a * b * Math.Cos( ga ) );

                                if ( distToPassedWp < Player.Profile.GetOptionValue<int>( "WAYPOINT_PASS" ) &&
                                    distFightstartToFightend >= distFightstartToCurrentwp )
                                {
                                    // check position of the waypoint after the current waypoint we want to reach
                                    // we don't check the closest wp, thats to much effort, we assume the distance between wp is
                                    // as far, that the next one is always the closest
                                    var nextWp = this.CurrentWaypointList.GetNextWayPoint( 1 );	// get current wp we try to reach +1
                                    var dirFightendToNextwp = Math.Atan2( nextWp.Z - Player.Z, nextWp.X - Player.X );
                                    var dirFightendToCurrentwp = Math.Atan2( currentWp.Z - Player.Z, currentWp.X - Player.X );
                                    angleDif = Helpers.AngleDifference( dirFightendToCurrentwp, dirFightendToNextwp );

                                    if ( Helpers.Deg( angleDif ) > Player.Profile.GetOptionValue<int>( "WAYPOINT_PASS_DEGR" ) )
                                    {
                                        if ( this.CurrentWaypointList.Current.HasAction )
                                        {
                                            this.CurrentWaypointList.Current.Execute();
                                        }

                                        Printf( Color.Green, Resources.WeOverrunWpMoveToNext, currentWp.Index, nextWp.Index );

                                        this.CurrentWaypointList.MoveNext();
                                    }
                                }
                            }
                        }
                        else
                        {
                            // don't fight, move to wp
                            if ( Player.TargetPtr != 0 )
                            {
                                Player.ClearTarget();
                            }

                            EggPet.CheckEggPets();

                            var wp = CurrentWaypointList.Current;

                            Printf( Color.Green, ( Player.Returning ? Resources.MovingToReturnPathWp : Resources.MovingToWp ) + " type: " + wp.Type, wp.Index, wp.X, wp.Z );

                            Player.CurrentWaypointType = wp.Type;		// remember current waypoint type
                            Task<bool?> t = null;
                            if ( wp.Type == WaypointType.Harvest )
                            {
                                //lets start a task, the task will run in background and, probably, will be completed by the time we arrive to the wp
                                t = new Task<bool?>( () =>
                                                    {
                                                        var r = Player.CanHarvestWaypoint( wp );
                                                        if ( DebugHarvest )
                                                        {
                                                            Warning( "Finisshed searching for harvestables on wp." );
                                                        }
                                                        return r;
                                                    } );
                                if ( DebugHarvest )
                                {
                                    Warning( "Starting background check for harvestables on wp." );
                                }
                                t.Start();
                            }
                            var resu = Player.MoveTo( wp, wp.Type == WaypointType.Travel || wp.Type == WaypointType.Run || wp.Type == WaypointType.Harvest, true );
                            var success = resu.Item1;
                            var reason = resu.Item2;
                            var skipHarvestCheck = false;
                            //Why would we check twice for skills and potions? if the wp succeeded we checked there...
                            if ( !success && !Player.Mounted )
                            {
                                Player.CheckPotions();
                                Player.CheckSkills( true );
                            }

                            if ( success ) //We moved successfully
                            {
                                Player.SuccessWaypoints++;
                                if ( Player.SuccessWaypoints > 3 )
                                {
                                    Player.UnstickCounter = 0;
                                }

                                if ( Player.Returning )
                                {
                                    if ( wp == this.ReturnWaypointList.Waypoints.Last() )
                                    {
                                        Player.Returning = false;
                                        this.WaypointList.Current = this.WaypointList.GetNearestWaypoint( wp.X, wp.Z );
                                        Print( Resources.CompletedReturnPath, Color.Yellow );
                                    }
                                    else
                                    {
                                        this.ReturnWaypointList.MoveNext();
                                    }
                                }
                                else
                                {
                                    this.WaypointList.MoveNext();
                                }

                                if ( t != null )
                                {
                                    t.Wait( 50 );
                                    skipHarvestCheck = t.Result.HasValue && ( bool ) !t.Result;
                                }

                                if ( wp.Type == WaypointType.Harvest && !skipHarvestCheck )
                                {
                                    while ( Player.HasAggro )
                                    {
                                        var tFight = Task.Factory.StartNew( Player.Fight );
                                        tFight.Wait();
                                        Thread.CurrentThread.Join( 100 );
                                    }
                                    Player.Harvest();
                                }
                                if ( wp.HasAction )
                                {
                                    if ( !( skipHarvestCheck && wp.Code.Trim() == "Player.Harvest();" ) )
                                    {
                                        Player.ReleaseKeys();
                                        Player.WaitTillStopMoving( 5 );
                                        wp.Execute();
                                        Thread.Sleep( 0 );
                                    }
                                    //HACK: This is to fix the Reverse()
                                    if ( Player.Returning && this.ReturnWaypointList.Current == wp )
                                    {
                                        this.ReturnWaypointList.MoveNext();
                                    }
                                    else if ( this.WaypointList.Current == wp )
                                    {
                                        this.WaypointList.MoveNext();
                                    }
                                }
                            }
                            else //Check why we didn't move
                            {
                                if ( reason != WpFailReason.WfTarget && reason != WpFailReason.WfCombat )
                                {
                                    Error( Resources.WaypointMovementFailed );
                                }

                                if ( reason == WpFailReason.WfCombat )
                                {
                                    Print( Resources.WeHaveAggroStopMovingToWaypoint, Color.Turquoise );
                                }

                                if ( reason == WpFailReason.WfDist )
                                {
                                    this.DistBreakCount++;
                                }
                                else
                                {
                                    this.DistBreakCount = 0;
                                }

                                if ( reason == WpFailReason.WfPullback )
                                {

                                }

                                if ( reason == WpFailReason.WfStuck || DistBreakCount > 3 )
                                {
                                    DistBreakCount = 0;
                                    Player.ClearTarget();
                                    Player.SuccessWaypoints = 0;
                                    if ( ( Player.Profile.GetOptionValue<int>( "MAX_UNSTICK_TRIALS" ) > 0 && Player.UnstickCounter >= Player.Profile.GetOptionValue<int>( "MAX_UNSTICK_TRIALS" ) ) || !Helpers.IsInGame )
                                    {
                                        if ( Helpers.IsInGame )
                                        {
                                            Printf( Color.Red, Resources.UnstickTriesGreaterThanMaximumAllowed, Player.UnstickCounter, Player.Profile[ "MAX_UNSTICK_TRIALS" ] );
                                            if ( Player.Profile.HasOnUnstickFailure )
                                            {
                                                Player.Profile.OnUnstickFailure();
                                            }
                                            else if ( Player.Profile.GetOptionValue<bool>( "LOGOUT_WHEN_STUCK" ) )
                                            {
                                                Player.Logout();
                                                Stop();
                                                return;
                                            }
                                            else
                                            {
                                                Player.UnstickCounter = 0;
                                                AssistMode = true;
                                            }
                                            continue;
                                        }

                                        Error( Resources.TheGameHasBeenDisconnected );
                                        Player.Stop();
                                        this.Stop();
                                        return;
                                    }

                                    if ( !AssistMode )
                                    {
                                        Player.Unstick();
                                        Player.UnstickCounter++;
                                    }
                                }
                            }
                        }
                    }

                    Thread.CurrentThread.Join( Helpers.Ping / 2 );
                }
                catch ( ThreadInterruptedException )
                {
                }
            }
        }

        public static bool DebugHarvest { get; set; }

        public void Resurrect()
        {
            Player.Stop();
            if ( !Helpers.IsInGame )
            {
                Error( Resources.TheGameHasBeenDisconnected );
                Stop();
                return;
            }
            Player.DeathCounter++;
            try
            {
                var bmp = User32.PrintWindow( Memory.WindowHandle );
                bmp.Save( DataPath + @"Screenshots\" + Player.Name + DateTime.Today.ToString( "yyyy-MM-dd" ) + ".bmp" );
            }
            catch
            {
                Error( "there was an error saving the death screenshot." );
            }
            if ( Player.Profile.GetOptionValue<bool>( "RES_AFTER_DEATH" ) )
            {
                Printf( Color.Red, "You have died, resurrecting..." ); // Died. Resurrecting player...

                Printf( Color.Green, "Resurrecting after 5 seconds" ); // try to resurrect in 5 seconds
                Helpers.Yrest( 5000 );

                // Try to revive with macro
                if ( !Player.Alive )
                {
                    Printf( Color.Green, "Using ingame resurrect macro" ); // use the ingame resurrect macro
                    Helpers.RoMScript( "UseSelfRevive();" ); // first try self revive
                    Helpers.Yrest( 500 );
                    Helpers.RoMScript( "BrithRevive();" );
                    Helpers.WaitForLoadingScreen( 30 );
                    Helpers.Yrest( Player.Profile.GetOptionValue<int>( "WAIT_TIME_AFTER_RES" ) );
                    Player.Update();
                }
            }
            Printf( Color.Green, "You have died {0} of {1} max times.", Player.DeathCounter, Player.Profile.GetOptionValue<int>( "MAX_DEATHS" ) );
            if ( Player.DeathCounter >= Player.Profile.GetOptionValue<int>( "MAX_DEATHS" ) )
            {
                Player.Logout();
            }
        }

        public void Pause()
        {
            RealTimer.Stop();
            Player.Pause();

            this.Paused = true;
            if ( tThread != null )
            {
                tThread.Abort();
                RunStart();
            }
            OnBotPaused();
            Debug( "Bot paused." );
        }

        public void Stop()
        {
            AssistMode = false;
            RealTimer.Stop();
            this.Stopped = true;
            this.Running = false;

            if ( Player != null )
            {
                Player.Stop();
            }

            if ( keyboardThread != null )
            {
                keyboardThread.Interrupt();
                keyboardThread.Join( 50 );
            }

            OnBotStopped();
            Debug( "Bot stopped." );
            if ( tThread != null )
            {
                tThread.Interrupt();
                tThread.Join( 50 );
            }
        }

        public void Resume()
        {
            if ( !Paused )
            {
                return;
            }

            var t = Task.Factory.StartNew( () =>
            {
                Player.Update();
                Player.Inventory.Update();
            } );
            
            Debug( "Bot resumed" );

            Player.LastDistImprove = DateTime.Now;
            RealTimer.Start();
            Paused = false;
            Player.Resume();
            //Give some time to player to resume...
            t.Wait( 100 );
            if ( tThread != null )
            {
                tThread.Abort();
            }
            RunStart();
        }

        public void Sleep()
        {
        }

        public static void Printf( string format, params object[] values )
        {
            Print( string.Format( format, values ), Color.White );
        }

        public static void Printf( Color color, string format, params object[] values )
        {
            Print( string.Format( format, values ), color );
        }

        public static void Print( string text )
        {
            Print( text, Color.White );
        }

        public static void Print( string text, Color color )
        {
            Logger.Log( text, color );
        }

        public static void Debug( object obj )
        {
            if ( obj == null )
            {
                Logger.Debug( "null" );
            }
            else if ( !( obj is string ) && obj is IEnumerable )
            {
                var s = new StringBuilder();
                foreach ( var o in ( IEnumerable ) obj )
                {
                    s.AppendLine( o != null ? string.Format( "{0}: {1}", o.GetType().Name, o ) : "null" );
                }
                Logger.Debug( s.ToString() );
            }
            else
            {
                Logger.Debug( obj is string ? ( string ) obj : obj.ToString() );
            }
        }

        public static void Warning( string text )
        {
            Logger.Warning( text );
        }

        public static void Error( string text )
        {
            Logger.Error( text );
        }

        public WaypointList WaypointList
        {
            get
            {
                return _waypoints;
            }
            set
            {
                _waypoints = value;
            }
        }

        public WaypointList ReturnWaypointList
        {
            get
            {
                return _returnWaypoints;
            }
            set
            {
                _returnWaypoints = value;
            }
        }

        public static Bot Instance
        {
            get { return Instancia; }
        }

        public static Bot Instancia
        {
            get { return _instancia ?? ( _instancia = new Bot() ); }
            //set { _instancia = value; }
        }

        public int ProcId
        {
            get;
            set;
        }

        public string Locale { get; set; }

        public Skills Skills
        {
            get
            {
                return _skills;
            }
        }

        private static uint? _maxCpuSpeed;
        private Thread tThread;
        private Thread keyboardThread;

        public static uint GetTimeFrequency
        {
            get
            {
                return _maxCpuSpeed.HasValue ? _maxCpuSpeed.Value : ( _maxCpuSpeed = GetMaxCpuSpeed() ).Value;
            }
        }

        private static uint GetMaxCpuSpeed()
        {
            using ( var managementObject = new ManagementObject( "Win32_Processor.DeviceID='CPU0'" ) )
            {
                var sp = ( uint ) ( managementObject[ "MaxClockSpeed" ] );

                return sp;
            }
        }

        public SynchronizationContext Contexto { get; set; }

        public string Log { get; set; }

        public DateTime LastSkillKeypressTime { get; set; }

        public static Player Player { get; set; }

        public bool Stopped { get; set; }

        public bool Running { get; set; }

        public bool Paused { get; set; }

        public int CommandMacro { get; set; }

        public int ResultMacro { get; set; }

        public bool Initialized { get; set; }

        public static bool DebugSkills { get; set; }

        public static IntPtr MainWindowHandle { get; set; }

        public static Dictionary<string, Func<object, object>> UserFunctions
        {
            get { return UserFunctionsDict; }
        }

        public static bool ControlCamera { get; set; }

        public static bool DebugLoot { get; set; }

        public static bool DebugItems { get; set; }

        public static bool DebugFight { get; set; }

        public static bool DebugWaypoints { get; set; }

        public TimeSpan TimeToLevel
        {
            get
            {
                var expPerMin = ExpPerMin;
                var needExp = Player.MaxExp - Player.Exp;
                var minutesToLevel = expPerMin > 0 ? needExp / expPerMin : 9999;
                return TimeSpan.FromMinutes( minutesToLevel );
            }
        }

        public int ExpPerMin
        {
            get
            {
                var diff = Player.Exp - Player.StartExp;
                return ( int ) ( RealTimer.Elapsed.TotalMinutes > 0 ? diff / RealTimer.Elapsed.TotalMinutes : 0 );
            }
        }

        public static bool DebugBuffs { get; set; }

        public static bool UseDatabaseHttpServer { get; set; }

        public int DistBreakCount { get; set; }
    }

    public class GameInstanceAddedEventArgs : EventArgs
    {
        public GameInstanceAddedEventArgs( string instanceName )
        {
            InstanceName = instanceName;
        }
        public string InstanceName { get; set; }
    }
}
